######################################
#                                    #
#  AutoArchiveManager - By Yingest   #
#                                    #
#    The Core Class for ZIPTaskss    #
#                                    #
######################################

import re
import os
import time
import threading
import logging
from queue import Queue
from subprocess import Popen, PIPE

try:
    from Modules.animate import animate
except ImportError:  # 回退./main.py时平行引用 __package__ 为 None
    from ..animate import animate

# Global setup for the file
# Path of 7za


class ArchiveTask:
    """ArchiveTask class for handling archive operations."""

    def __init__(self, zip_Opts: list[str], new_name: str, path: str,
                 target_path: str, logger: logging.Logger):
        self.PATH_7ZA = r'.\Modules\Sevenzip\7z2409-extra\7za.exe'
        self.zip_Opts = " ".join(zip_Opts)
        self.new_name = new_name
        self.path = path
        self.target_path = target_path
        self.new_path = ""
        self.logger = logger

    def _szipcore(self, cmd: str, msg_q: Queue, zipend: threading.Event):
        total, status = "", ""
        state, progress, msg, extra = -1, -1, "", ""
        #-- Please refer to Ziplog.txt for 7Zip Output
        pattern1 = re.compile(
            r'^(?!Add new data to archive:)(\d+) folders, (\d+) files, (\d+) bytes \((\d+) [KMGT]iB\)'
        )
        pattern2 = re.compile(r"M?%?\s+\d*\s*(Scan|Removing|\+)\s+")
        zipreturn = [
            "7-Zip", "Scanning the drive:", "state2", "state3",
            "Creating archive:", "Add new data to archive:", "state6",
            "Files read from disk:", "Archive size:", "state9",
            "Everything is Ok", "WARNING:"
        ]
        i, found = 0, False
        with Popen(cmd, stdout=PIPE, bufsize=1,
                   universal_newlines=True) as ret:
            for line in ret.stdout:
                li = line.rstrip()
                if not li.replace(" ", ""):  # r"   "
                    continue
                i, found = 0, False
                for zret in zipreturn:
                    if li.startswith(zret):
                        found = True
                        state, msg, extra = (i, zret, li[len(zret):])
                        if state == 5:
                            mat = re.search(r'(\d+)\s+files', li)
                            if mat: total = str(mat.group(1))
                        break
                    i += 1
                if not found:
                    if state == 11: state, msg = (-1, li)
                    elif pattern1.search(li): state, msg, extra = (3, "", li)
                    elif li == "  0%":
                        state = 6
                        progress = 0
                        status = f" {0:0{len(total)}d}/{total}[{0:02d}%] "
                        msg = f"{status}"
                        extra = ""
                    elif pattern2.search(li):
                        out = [s for s in li.split(" ") if s.strip()]
                        filename = (out[-1]).split("\\")[-1]
                        if len(filename) > 25: filename = ".." + filename[-25:]
                        match pattern2.search(li).group(1):
                            case "+":
                                state = 6
                                progress = int(out[0][:-1])
                                matches = re.search(r"%\s+(\d+)\s*+\+", li)
                                done = (int(matches.group(1))
                                        if matches else 0)
                                msg = f" {done:0{len(total)}d}/{total}[{progress:02d}%] "
                                extra = " + " + filename
                            case others:  #Scan, Removing
                                state = 2 if others == "Scan" else 9
                                progress = -1
                                matches = re.search(
                                    r"M\s+(\d+)\s*Scan" if state == 2 else
                                    r"M\s+(\d+)\s*Removing", li)
                                done = (int(matches.group(1))
                                        if matches else 0)
                                msg = f"{out[0]}/{done}  "
                                extra = f"{others[0]} {filename}"
                    else:
                        continue
                msg_q.put((state, progress, msg, extra))
        msg_q.put((12, ret.returncode, "", ""))
        while not msg_q.empty():
            time.sleep(0.5)
        zipend.set()
        return

    def run(self, flush_locker):
        """Run the Archive"""
        list_txt = os.path.join(self.path, "Files List_by Yingest.txt")
        if os.path.exists(list_txt):
            try:
                os.remove(list_txt)
            except Exception as e:
                self.logger.error(
                    f"Error removing file, Maybe another process is working: {e}"
                )
                return -88
        new_name = f"{self.new_name}-{time.strftime('%y%m%d%H%M%S', time.localtime())}"
        new_path_name = os.path.join(self.target_path, new_name)
        zip_type = "7z" if "-t" not in self.zip_Opts else re.search(
            r'-t(\S+)', self.zip_Opts).group(1)
        zip_txt = f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} {self.path} ==> {new_path_name}.{zip_type}"
        flush_locker(f"\n{zip_txt}  ⋙")
        self.new_path = f'{new_path_name}.{zip_type}'
        with open(list_txt, 'w', encoding='utf-8') as fs:
            fs.write(
                f"Files List  _  by Yingest\n{zip_txt}\r\nZipped Items:\n" +
                '\n'.join(os.listdir(self.path)))
        alt = open(list_txt)
        # 7za cmd= exepath + switch + newzipname + oldPath
        # Please refer to https://documentation.help/7-Zip-18.0/index6.html for support.
        cmd = f'"{self.PATH_7ZA}" a -bso1 -bsp1 -bse1 {self.zip_Opts} "{new_path_name}" "{self.path}"'
        zipend = threading.Event()
        msg_q = Queue()
        zip_thread = threading.Thread(target=self._szipcore,
                                      args=(cmd, msg_q, zipend))
        zip_thread.daemon = True
        ani = animate(25, 12)
        state, laststate, timestamp, progress, msg, extra = 0, 0, "", 0, "", ""
        flash, disp, = "", ""
        zip_thread.start()
        while True:
            time.sleep(0.15)
            timestamp = time.strftime("[%H:%M:%S]", time.localtime())
            if not msg_q.empty():
                state, progress, msg, extra = msg_q.get()
                match state:
                    case 0:
                        pass  #print("-> ZipStart <-")
                    case 1 | 3 | 4 | 5 | 7 | 8 | 10:
                        print(flash + f"{timestamp}  {msg}{extra}")
                        flash = ""
                    case 2 | 6 | 9:
                        disp = ani.next1(progress) + ani.next2(
                        ) + "|" + msg + ani.timetake() + extra
                        print(flash + disp, end='')
                        flash = "\r" + len(disp) * " " + "\r"
                    case 12:
                        # print("-> ZipEnd   <-")
                        returncode = progress
                laststate = state
                if laststate + 1 == state: ani.reset()
            elif state == 2 or state == 6 or state == 9:
                disp = ani.next1(progress) + ani.next2(
                ) + "|" + msg + ani.timetake() + extra
                print(flash + disp, end='')
                flash = "\r" + len(disp) * " " + "\r"
            laststate = state
            if zipend.is_set():
                break
        zip_thread.join()
        alt.close()
        return returncode


if __name__ == "__main__":
    zip_opts = ['-t7z', '-mx5', '-mmt2']
    newname = r'C:\Users\tding1\Downloads\123'
    target_path_name = r'C:\Users\tding1\Downloads\123.7z'
    file_path_name = r'C:\Users\tding1\Downloads\123'
    sz = ArchiveTask(zip_opts, newname, file_path_name, None)
    result = sz.sevenzip(" ".join(zip_opts), target_path_name, file_path_name)
